<?php

namespace Scau\UnifiedLogin;

use Scau\Common\Utils;
use Scau\Core\ScauBase;
use Scau\Core\ScauContants;
use Scau\Exception\UnifiedLoginException;

/**
 * 授权类
 * @package Scau\UnifiedLogin
 */
class Authorization extends ScauBase
{
    /**
     * Authorization constructor.
     * @param string $appId 应用ID
     * @param string $appSecret 应用密钥
     * @param string $redirectUri 回调地址，无需url_encode()编码
     * @param array $config 配置
     */
    public function __construct(string $appId, string $appSecret, string $redirectUri, array $config = [])
    {
        $this->appId = $appId;
        $this->appSecret = $appSecret;
        $this->redirectUri = $redirectUri;
        $this->config = array_merge($this->getDefaultConfig(), $config);
    }

    /**
     * 获取授权链接
     * @param string $appId 应用ID
     * @param string $redirectUri 回调地址
     * @param string $state 此参数会原样返回
     * @param string $defaultGrant 默认授权方式
     * @param string $scopes 需要用户授权的权限，多种权限使用逗号隔开
     * @param bool $env 环境，本地测试用，请不要传递此参数
     */
    public static function getGrantUrl(
        string $appId,
        string $redirectUri,
        string $state = '',
        string $defaultGrant = ScauContants::OAUTH_DEFAULT_GRANT_ACCOUNT,
        string $scopes = ScauContants::SCOPE_GET_USER_INFO,
        bool $env = true
    ): string
    {
        $params = [
            'app_id' => $appId,
            'redirect_uri' => $redirectUri,
            'response_type' => 'code',
            'state' => $state,
            'default_grant' => $defaultGrant,
            'scopes' => $scopes
        ];
        return Utils::urlencodeParameters(($env ? ScauContants::OAUTH_DOMAIN_PRODUCTION : ScauContants::OAUTH_DOMAIN_SANDBOX) . "/oauth/grant", $params);
    }

    /**
     * 获取授权页面的url
     * @param string $state 此参数会原样返回
     * @return string
     * @deprecated 请使用Authorization::getGrantUrl()
     */
    public function getOauthUrl(string $state = ''): string
    {
        $params = [
            'app_id' => $this->getAppId(),
            'redirect_uri' => $this->getRedirectUri(),
            'response_type' => 'code',
            'state' => $state
        ];

        if ($this->getDefaultGrant() === ScauContants::OAUTH_DEFAULT_GRANT_WECHAT) {
            $params['default_grant'] = ScauContants::OAUTH_DEFAULT_GRANT_WECHAT;
        }

        return Utils::urlencodeParameters($this->getOauthDomain() . "/oauth/grant", $params);
    }

    /**
     * 通过code获取用户开放信息
     * @param string $code 授权后返回的code
     * @param array $options 其他选项
     * @return array
     * @throws UnifiedLoginException
     */
    public function credential(string $code, array $options = []): array
    {
        $this->getAccessToken();
        $openid = $this->getOpenid($code);
        $userInfo = $this->getUserInfo($openid, $options);
        $userInfo['openid'] = $openid;// 加上openid返回
        return $userInfo;
    }

    /**
     * 获取access token
     * @return string
     * @throws UnifiedLoginException
     */
    public function getAccessToken(): string
    {
        $tokenFilePath = $this->getAccessTokenFilePath();
        $accessToken = '';
        if (file_exists($tokenFilePath)) {
            $tokenData = json_decode(file_get_contents($tokenFilePath), true);
            if ($tokenData && $tokenData['access_token'] && time() < $tokenData['expire_time'] - 600) {
                $accessToken = $tokenData['access_token'];
            }
        }

        if (!$accessToken) {
            $url = Utils::urlencodeParameters(
                $this->getApiDomain() . "/applications/getAccessToken",
                [
                    'app_id' => $this->getAppId(),
                    'app_secret' => $this->getAppSecret()
                ]
            );
            $data = Utils::httpCurl($url);
            $data = json_decode($data, true);
            if (!$data) {
                throw new UnifiedLoginException('An unexpected exception occurred!');
            }
            if ($data['code']) {
                throw new UnifiedLoginException($data['msg']);
            }

            $accessToken = $data['data']['access_token'];
            $tokenData = $data['data'];
            $tokenData['expire_time'] = $tokenData['create_time'] + $tokenData['expire_in'];
            unset($tokenData['create_time'], $tokenData['expire_in']);
            file_put_contents($tokenFilePath, json_encode($tokenData));
        }

        $this->accessToken = $accessToken;

        return $accessToken;
    }

    /**
     * 获取用户应用的openid
     * @param string $code
     * @return string
     * @throws UnifiedLoginException
     */
    public function getOpenid(string $code): string
    {
        $url = Utils::urlencodeParameters(
            $this->getApiDomain() . "/user/getOpenid",
            [
                'app_id' => $this->getAppId(),
                'access_token' => $this->accessToken,
                'code' => $code
            ]
        );
        $data = Utils::httpCurl($url);
        $data = json_decode($data, true);
        if (!$data) {
            throw new UnifiedLoginException('An unexpected exception occurred!');
        }
        if ($data['code']) {
            throw new UnifiedLoginException($data['msg']);
        }

        return $data['data']['openid'];
    }

    /**
     * 获取用户开放信息
     * @param string $openid 
     * @param array $options 其他选项
     * @return array
     * @throws UnifiedLoginException
     */
    public function getUserInfo(string $openid, array $options = []): array
    {
        $params = [
            'app_id' => $this->getAppId(),
            'access_token' => $this->accessToken,
            'openid' => $openid
        ];
        if ($options) {
            $params = array_merge($params, $options);
        }

        $url = Utils::urlencodeParameters(
            $this->getApiDomain() . "/user/getInfo",
            $params
        );
        $data = Utils::httpCurl($url);
        $data = json_decode($data, true);
        if (!$data) {
            throw new UnifiedLoginException('An unexpected exception occurred!');
        }
        if ($data['code']) {
            throw new UnifiedLoginException($data['msg']);
        }

        return $data['data'];
    }
}